package com.wy.divide;

import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.UUID;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.MediaType;
import org.springframework.http.converter.FormHttpMessageConverter;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.client.endpoint.DefaultAuthorizationCodeTokenResponseClient;
import org.springframework.security.oauth2.client.endpoint.OAuth2AccessTokenResponseClient;
import org.springframework.security.oauth2.client.endpoint.OAuth2AuthorizationCodeGrantRequest;
import org.springframework.security.oauth2.client.http.OAuth2ErrorResponseErrorHandler;
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
import org.springframework.security.oauth2.client.web.DefaultOAuth2AuthorizationRequestResolver;
import org.springframework.security.oauth2.client.web.OAuth2AuthorizationRequestRedirectFilter;
import org.springframework.security.oauth2.client.web.OAuth2AuthorizationRequestResolver;
import org.springframework.security.oauth2.core.AuthorizationGrantType;
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
import org.springframework.security.oauth2.core.http.converter.OAuth2AccessTokenResponseHttpMessageConverter;
import org.springframework.security.oauth2.core.oidc.OidcScopes;
import org.springframework.security.oauth2.jwt.JwtDecoder;
import org.springframework.security.oauth2.server.authorization.JdbcOAuth2AuthorizationConsentService;
import org.springframework.security.oauth2.server.authorization.JdbcOAuth2AuthorizationService;
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationConsentService;
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
import org.springframework.security.oauth2.server.authorization.client.JdbcRegisteredClientRepository;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
import org.springframework.security.oauth2.server.authorization.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration;
import org.springframework.security.oauth2.server.authorization.config.annotation.web.configurers.OAuth2AuthorizationServerConfigurer;
import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings;
import org.springframework.security.oauth2.server.authorization.settings.ClientSettings;
import org.springframework.security.oauth2.server.authorization.token.JwtEncodingContext;
import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenCustomizer;
import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenGenerator;
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter;
import org.springframework.security.oauth2.server.resource.authentication.JwtGrantedAuthoritiesConverter;
import org.springframework.security.web.DefaultSecurityFilterChain;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.util.UrlUtils;
import org.springframework.security.web.util.matcher.MediaTypeRequestMatcher;
import org.springframework.util.ObjectUtils;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;

import com.nimbusds.jose.jwk.JWKSet;
import com.nimbusds.jose.jwk.RSAKey;
import com.nimbusds.jose.jwk.source.ImmutableJWKSet;
import com.nimbusds.jose.jwk.source.JWKSource;
import com.nimbusds.jose.proc.SecurityContext;
import com.wy.constant.ConstAuthorizationServerRedis;
import com.wy.context.RedisSecurityContextRepository;
import com.wy.core.CustomizerAuthorizationGrantType;
import com.wy.grant.SmsAuthenticationConverter;
import com.wy.grant.SmsAuthenticationProvider;
import com.wy.helpers.SecurityContextOAuth2Helpers;
import com.wy.oauth.customizer.CustomizerAuthorizationServerSettings;
import com.wy.provider.device.DeviceClientAuthenticationConverter;
import com.wy.provider.device.DeviceClientAuthenticationProvider;
import com.wy.token.CustomizerTokenCustomizer;

import dream.flying.flower.autoconfigure.redis.helper.RedisStrHelpers;
import dream.flying.flower.framework.security.constant.ConstSecurity;
import dream.flying.flower.framework.security.entrypoint.UnauthorizedAuthenticationEntryPoint;
import dream.flying.flower.framework.security.handler.CustomizerAuthenticationFailureHandler;
import dream.flying.flower.framework.security.handler.CustomizerAuthenticationSuccessHandler;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;

/**
 * 实现授权确认页面的分离,同时设备码流程的授权确认页面与授权码流程的授权确认页面是同一个,也需要兼容一下.设备码流程中有一个校验设备码的页面,也需要分离出来
 * 
 * <pre>
 * 1.重定向至授权确认页面时直接携带相关参数重定向至前端项目中
 * 2.提供接口查询登录用户在发起授权的客户端中相关scope信息
 * 3.重定向至设备码校验页面时携带当前sessionId(nonceId)重定向至前端项目中
 * 4.编写授权确认失败处理类,在调用确认授权接口失败时响应json
 * 5.编写授权成功处理类,在调用授权确认接口成功时响应json
 * 6.编写校验设备码成功响应类,在校验设备码成功后响应json
 * 7.修改重定向至登录页面处理,兼容在请求校验设备码时登录信息过期处理
 * 8.将以上内容添加至认证服务配置中
 * 9.前端项目中编写授权确认、设备码校验、设备码校验成功页面
 * </pre>
 * 
 * 前后端分离设备授权确认页:Consent.vue;设备码验证页面:Activate.vue;设备码验证成功页面:Activated.vue
 * 
 * @author 飞花梦影
 * @date 2024-11-04 17:27:49
 * @git {@link https://github.com/dreamFlyingFlower}
 */
@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
@EnableMethodSecurity(jsr250Enabled = true, securedEnabled = true)
public class DeviceConfig {

	private final RedisStrHelpers redisStrHelpers;

	/**
	 * 登录地址,前后端分离就填写完整的url路径,不分离填写相对路径
	 */
	private final String LOGIN_URL = "http://127.0.0.1:5173/login";

	private static final String CUSTOM_CONSENT_REDIRECT_URI = "/oauth2/consent/redirect";

	private static final String CUSTOM_DEVICE_REDIRECT_URI = "/activate/redirect";

	private final RedisSecurityContextRepository redisSecurityContextRepository;

	/**
	 * 配置端点的过滤器链
	 *
	 * @param http spring security核心配置类
	 * @return 过滤器链
	 * @throws Exception 抛出
	 */
	@Bean
	SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http,
			RegisteredClientRepository registeredClientRepository,
			CustomizerAuthorizationServerSettings authorizationServerSettings) throws Exception {
		// 配置默认的设置,忽略认证端点的csrf校验
		OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http);

		// 添加跨域过滤器
		http.addFilter(corsFilter());
		// 禁用 csrf 与 cors
		http.csrf(AbstractHttpConfigurer::disable);
		http.cors(AbstractHttpConfigurer::disable);

		// 新建设备码converter和provider
		DeviceClientAuthenticationConverter deviceClientAuthenticationConverter =
				new DeviceClientAuthenticationConverter(authorizationServerSettings.getDeviceAuthorizationEndpoint());
		DeviceClientAuthenticationProvider deviceClientAuthenticationProvider =
				new DeviceClientAuthenticationProvider(registeredClientRepository);

		// 使用redis存储、读取登录的认证信息
		http.securityContext(context -> context.securityContextRepository(redisSecurityContextRepository));

		http.getConfigurer(OAuth2AuthorizationServerConfigurer.class)
				// 开启OpenID Connect 1.0协议相关端点
				.oidc(Customizer.withDefaults())
				// 设置自定义用户确认授权页
				.authorizationEndpoint(authorizationEndpoint -> {
					// 校验授权确认页面是否为完整路径；是否是前后端分离的页面
					boolean absoluteUrl = UrlUtils.isAbsoluteUrl(ConstSecurity.CONSENT_PAGE_URI);
					// 如果是分离页面则重定向,否则转发请求
					authorizationEndpoint
							.consentPage(absoluteUrl ? CUSTOM_CONSENT_REDIRECT_URI : ConstSecurity.CONSENT_PAGE_URI);
					if (absoluteUrl) {
						// 适配前后端分离的授权确认页面,成功/失败响应json
						authorizationEndpoint.errorResponseHandler(new ConsentAuthenticationFailureHandler());
						authorizationEndpoint.authorizationResponseHandler(new ConsentAuthorizationResponseHandler());
					}
				})
				// 设置设备码用户验证url(自定义用户验证页)
				.deviceAuthorizationEndpoint(deviceAuthorizationEndpoint -> deviceAuthorizationEndpoint.verificationUri(
						UrlUtils.isAbsoluteUrl(DEVICE_ACTIVATE_URI) ? CUSTOM_DEVICE_REDIRECT_URI : DEVICE_ACTIVATE_URI))
				// 设置验证设备码用户确认页面
				.deviceVerificationEndpoint(deviceVerificationEndpoint -> {
					// 校验授权确认页面是否为完整路径；是否是前后端分离的页面
					boolean absoluteUrl = UrlUtils.isAbsoluteUrl(CONSENT_PAGE_URI);
					// 如果是分离页面则重定向,否则转发请求
					deviceVerificationEndpoint
							.consentPage(absoluteUrl ? CUSTOM_CONSENT_REDIRECT_URI : CONSENT_PAGE_URI);
					if (absoluteUrl) {
						// 适配前后端分离的授权确认页面,失败响应json
						deviceVerificationEndpoint.errorResponseHandler(new ConsentAuthenticationFailureHandler());
					}
					// 如果授权码验证页面或者授权确认页面是前后端分离的
					if (UrlUtils.isAbsoluteUrl(DEVICE_ACTIVATE_URI) || absoluteUrl) {
						// 添加响应json处理
						deviceVerificationEndpoint
								.deviceVerificationResponseHandler(new DeviceAuthorizationResponseHandler());
					}
				})
				.clientAuthentication(clientAuthentication ->
				// 客户端认证添加设备码的converter和provider
				clientAuthentication.authenticationConverter(deviceClientAuthenticationConverter)
						.authenticationProvider(deviceClientAuthenticationProvider));
		http
				// 当未登录时访问认证端点时重定向至login页面
				.exceptionHandling((exceptions) -> exceptions.defaultAuthenticationEntryPointFor(
						new UnauthorizedAuthenticationEntryPoint(LOGIN_URL),
						new MediaTypeRequestMatcher(MediaType.TEXT_HTML)))
				// 处理使用access token访问用户信息端点和客户端注册端点
				.oauth2ResourceServer((resourceServer) -> resourceServer.jwt(Customizer.withDefaults()));

		// 自定义短信认证登录转换器
		SmsAuthenticationConverter converter = new SmsAuthenticationConverter();
		// 自定义短信认证登录认证提供
		SmsAuthenticationProvider provider = new SmsAuthenticationProvider();
		http.getConfigurer(OAuth2AuthorizationServerConfigurer.class)
				// 让认证服务器元数据中有自定义的认证方式
				.authorizationServerMetadataEndpoint(metadata -> metadata.authorizationServerMetadataCustomizer(
						customizer -> customizer.grantType(ConstSecurity.GRANT_TYPE_SMS_CODE)))
				// 添加自定义grant_type——短信认证登录
				.tokenEndpoint(tokenEndpoint -> tokenEndpoint.accessTokenRequestConverter(converter)
						.authenticationProvider(provider));

		DefaultSecurityFilterChain build = http.build();

		// 从框架中获取provider中所需的bean
		OAuth2TokenGenerator<?> tokenGenerator = http.getSharedObject(OAuth2TokenGenerator.class);
		AuthenticationManager authenticationManager = http.getSharedObject(AuthenticationManager.class);
		OAuth2AuthorizationService authorizationService = http.getSharedObject(OAuth2AuthorizationService.class);
		// 以上三个bean在build()方法之后调用是因为调用build方法时框架会尝试获取这些类,
		// 如果获取不到则初始化一个实例放入SharedObject中,所以要在build方法调用之后获取
		// 在通过set方法设置进provider中,但是如果在build方法之后调用authenticationProvider(provider)
		// 框架会提示unsupported_grant_type,因为已经初始化完了,在添加就不会生效了
		provider.setTokenGenerator(tokenGenerator);
		provider.setAuthorizationService(authorizationService);
		provider.setAuthenticationManager(authenticationManager);

		return build;
	}

	/**
	 * 配置认证相关的过滤器链(资源服务,客户端配置)
	 *
	 * @param http spring security核心配置类
	 * @return 过滤器链
	 * @throws Exception 抛出
	 */
	@Bean
	SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http,
			ClientRegistrationRepository clientRegistrationRepository) throws Exception {
		// 添加跨域过滤器
		http.addFilter(corsFilter());
		// 禁用 csrf 与 cors
		http.csrf(AbstractHttpConfigurer::disable);
		http.cors(AbstractHttpConfigurer::disable);
		http.authorizeHttpRequests((authorize) -> authorize
				// 放行静态资源
				.requestMatchers("/assets/**", "/webjars/**", "/login", "/getCaptcha", "/getSmsCaptcha", "/error",
						"/oauth2/consent/parameters")
				.permitAll()
				.anyRequest()
				.authenticated())
				// 指定登录页面
				.formLogin(formLogin -> {
					formLogin.loginPage("/login");
					if (UrlUtils.isAbsoluteUrl(LOGIN_URL)) {
						// 绝对路径代表是前后端分离,登录成功和失败改为写回json,不重定向了
						formLogin.successHandler(new CustomizerAuthenticationSuccessHandler());
						formLogin.failureHandler(new CustomizerAuthenticationFailureHandler());
					}
				});
		// 添加BearerTokenAuthenticationFilter,将认证服务当做一个资源服务,解析请求头中的token
		http.oauth2ResourceServer((resourceServer) -> resourceServer.jwt(Customizer.withDefaults())
				.accessDeniedHandler(SecurityContextOAuth2Helpers::exceptionHandler)
				.authenticationEntryPoint(SecurityContextOAuth2Helpers::exceptionHandler));
		// 兼容前后端分离与不分离配置
		http
				// 当未登录时访问认证端点时重定向至login页面
				.exceptionHandling((exceptions) -> exceptions.defaultAuthenticationEntryPointFor(
						new UnauthorizedAuthenticationEntryPoint(LOGIN_URL),
						new MediaTypeRequestMatcher(MediaType.TEXT_HTML)));
		// 联合身份认证
		http.oauth2Login(oauth2Login -> oauth2Login.loginPage(LOGIN_URL)
				.authorizationEndpoint(authorization -> authorization
						.authorizationRequestResolver(this.authorizationRequestResolver(clientRegistrationRepository)))
				.tokenEndpoint(token -> token.accessTokenResponseClient(this.accessTokenResponseClient())));

		// 使用redis存储、读取登录的认证信息
		http.securityContext(context -> context.securityContextRepository(redisSecurityContextRepository));

		return http.build();
	}

	/**
	 * AuthorizationRequest 自定义配置
	 *
	 * @param clientRegistrationRepository yml配置中客户端信息存储类
	 * @return OAuth2AuthorizationRequestResolver
	 */
	private OAuth2AuthorizationRequestResolver
			authorizationRequestResolver(ClientRegistrationRepository clientRegistrationRepository) {
		DefaultOAuth2AuthorizationRequestResolver authorizationRequestResolver =
				new DefaultOAuth2AuthorizationRequestResolver(clientRegistrationRepository,
						OAuth2AuthorizationRequestRedirectFilter.DEFAULT_AUTHORIZATION_REQUEST_BASE_URI);

		// 整合oauth2-client客户端,兼容微信登录授权申请
		// authorizationRequestResolver.setAuthorizationRequestCustomizer(new
		// WechatAuthorizationRequestConsumer());

		return authorizationRequestResolver;
	}

	/**
	 * 适配微信登录适配,添加自定义请求token入参处理
	 *
	 * @return OAuth2AccessTokenResponseClient accessToken响应信息处理
	 */
	private OAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> accessTokenResponseClient() {
		DefaultAuthorizationCodeTokenResponseClient tokenResponseClient =
				new DefaultAuthorizationCodeTokenResponseClient();
		// 整合oauth2-client客户端,兼容微信登录授权申请
		// tokenResponseClient.setRequestEntityConverter(new
		// WechatCodeGrantRequestEntityConverter());
		// 自定义 RestTemplate,适配微信登录获取token
		OAuth2AccessTokenResponseHttpMessageConverter messageConverter =
				new OAuth2AccessTokenResponseHttpMessageConverter();
		List<MediaType> mediaTypes = new ArrayList<>(messageConverter.getSupportedMediaTypes());
		// 微信获取token时响应的类型为“text/plain”,这里特殊处理一下
		// 整合oauth2-client客户端,兼容微信登录授权申请
		// mediaTypes.add(MediaType.TEXT_PLAIN);
		// messageConverter.setAccessTokenResponseConverter(new
		// WechatMapAccessTokenResponseConverter());
		messageConverter.setSupportedMediaTypes(mediaTypes);

		// 初始化RestTemplate
		RestTemplate restTemplate = new RestTemplate(Arrays.asList(new FormHttpMessageConverter(), messageConverter));

		restTemplate.setErrorHandler(new OAuth2ErrorResponseErrorHandler());
		tokenResponseClient.setRestOperations(restTemplate);
		return tokenResponseClient;
	}

	/**
	 * 跨域过滤器配置
	 *
	 * @return CorsFilter
	 */
	@Bean
	CorsFilter corsFilter() {

		// 初始化cors配置对象
		CorsConfiguration configuration = new CorsConfiguration();

		// 设置允许跨域的域名,如果允许携带cookie的话,路径就不能写*号, *表示所有的域名都可以跨域访问
		configuration.addAllowedOrigin("http://127.0.0.1:5173");
		configuration.addAllowedOrigin("http://192.168.1.102:5173");
		// 设置跨域访问可以携带cookie
		configuration.setAllowCredentials(true);
		// 允许所有的请求方法 ==> GET POST PUT Delete
		configuration.addAllowedMethod("*");
		// 允许携带任何头信息
		configuration.addAllowedHeader("*");

		// 初始化cors配置源对象
		UrlBasedCorsConfigurationSource configurationSource = new UrlBasedCorsConfigurationSource();

		// 给配置源对象设置过滤的参数
		// 参数一: 过滤的路径 == > 所有的路径都要求校验是否跨域
		// 参数二: 配置类
		configurationSource.registerCorsConfiguration("/**", configuration);

		// 返回配置好的过滤器
		return new CorsFilter(configurationSource);
	}

	/**
	 * 自定义jwt解析器,设置解析出来的权限信息的前缀与在jwt中的key
	 *
	 * @return jwt解析器 JwtAuthenticationConverter
	 */
	@Bean
	JwtAuthenticationConverter jwtAuthenticationConverter() {
		JwtGrantedAuthoritiesConverter grantedAuthoritiesConverter = new JwtGrantedAuthoritiesConverter();
		// 设置解析权限信息的前缀,设置为空是去掉前缀
		grantedAuthoritiesConverter.setAuthorityPrefix("");
		// 设置权限信息在jwt claims中的key
		grantedAuthoritiesConverter.setAuthoritiesClaimName(ConstSecurity.AUTHORITIES_KEY);

		JwtAuthenticationConverter jwtAuthenticationConverter = new JwtAuthenticationConverter();
		jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(grantedAuthoritiesConverter);
		return jwtAuthenticationConverter;
	}

	/**
	 * 将AuthenticationManager注入ioc中,其它需要使用地方可以直接从ioc中获取
	 *
	 * @param authenticationConfiguration 导出认证配置
	 * @return AuthenticationManager 认证管理器
	 */
	@Bean
	@SneakyThrows
	AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) {
		return authenticationConfiguration.getAuthenticationManager();
	}

	/**
	 * 配置密码解析器,使用BCrypt的方式对密码进行加密和验证
	 *
	 * @return BCryptPasswordEncoder
	 */
	@Bean
	PasswordEncoder passwordEncoder() {
		return new BCryptPasswordEncoder();
	}

	/**
	 * 配置客户端Repository
	 *
	 * @param jdbcTemplate db 数据源信息
	 * @param passwordEncoder 密码解析器
	 * @return 基于数据库的repository
	 */
	@Bean
	RegisteredClientRepository registeredClientRepository(JdbcTemplate jdbcTemplate, PasswordEncoder passwordEncoder) {
		RegisteredClient registeredClient = RegisteredClient.withId(UUID.randomUUID().toString())
				// 客户端id
				.clientId("messaging-client")
				// 客户端秘钥,使用密码解析器加密
				.clientSecret(passwordEncoder.encode("123456"))
				// 客户端认证方式,基于请求头的认证
				.clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
				// 配置资源服务器使用该客户端获取授权时支持的方式
				.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
				.authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN)
				.authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)
				// 客户端添加自定义认证
				.authorizationGrantType(new AuthorizationGrantType(ConstSecurity.GRANT_TYPE_SMS_CODE))
				// 授权码模式回调地址,oauth2.1已改为精准匹配,不能只设置域名,并且屏蔽了localhost,本机使用127.0.0.1访问
				.redirectUri("http://127.0.0.1:8080/login/oauth2/code/messaging-client-oidc")
				.redirectUri("https://www.baidu.com")
				// 该客户端的授权范围,OPENID与PROFILE是IdToken的scope,获取授权时请求OPENID的scope时认证服务会返回IdToken
				.scope(OidcScopes.OPENID)
				.scope(OidcScopes.PROFILE)
				// 自定scope
				.scope("message.read")
				.scope("message.write")
				// 客户端设置,设置用户需要确认授权
				.clientSettings(ClientSettings.builder().requireAuthorizationConsent(true).build())
				.build();

		// 基于db存储客户端,还有一个基于内存的实现 InMemoryRegisteredClientRepository
		JdbcRegisteredClientRepository registeredClientRepository = new JdbcRegisteredClientRepository(jdbcTemplate);

		// 初始化客户端
		RegisteredClient repositoryByClientId =
				registeredClientRepository.findByClientId(registeredClient.getClientId());
		if (repositoryByClientId == null) {
			registeredClientRepository.save(registeredClient);
		}
		// 设备码授权客户端
		RegisteredClient deviceClient = RegisteredClient.withId(UUID.randomUUID().toString())
				.clientId("device-message-client")
				// 公共客户端
				.clientAuthenticationMethod(ClientAuthenticationMethod.NONE)
				// 设备码授权
				.authorizationGrantType(CustomizerAuthorizationGrantType.DEVICE_CODE)
				.authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN)
				// 自定scope
				.scope("message.read")
				.scope("message.write")
				.build();
		RegisteredClient byClientId = registeredClientRepository.findByClientId(deviceClient.getClientId());
		if (byClientId == null) {
			registeredClientRepository.save(deviceClient);
		}

		// PKCE客户端
		RegisteredClient pkceClient = RegisteredClient.withId(UUID.randomUUID().toString())
				.clientId("pkce-message-client")
				// 公共客户端
				.clientAuthenticationMethod(ClientAuthenticationMethod.NONE)
				// 授权码模式,因为是扩展授权码流程,所以流程还是授权码的流程,改变的只是参数
				.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
				.authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN)
				// 授权码模式回调地址,oauth2.1已改为精准匹配,不能只设置域名,并且屏蔽了localhost,本机使用127.0.0.1访问
				.redirectUri("http://127.0.0.1:8080/login/oauth2/code/messaging-client-oidc")
				.clientSettings(ClientSettings.builder().requireProofKey(Boolean.TRUE).build())
				// 自定scope
				.scope("message.read")
				.scope("message.write")
				.build();
		RegisteredClient findPkceClient = registeredClientRepository.findByClientId(pkceClient.getClientId());
		if (findPkceClient == null) {
			registeredClientRepository.save(pkceClient);
		}
		return registeredClientRepository;
	}

	/**
	 * 自定义JWT方式向token以及id_token中自定义存数据,自定义的数据可以从Authentication中获取.定义后会自动注入并应用
	 * 
	 * {@link SecurityContextHolder#getContext()}->{@link SecurityContext#getAuthentication()}
	 * 
	 * @return OAuth2TokenCustomizer的实例
	 */
	@Bean
	OAuth2TokenCustomizer<JwtEncodingContext> oauth2TokenCustomizer() {
		return new CustomizerTokenCustomizer();
	}

	/**
	 * 配置基于db的oauth2的授权管理服务
	 *
	 * @param jdbcTemplate db数据源信息
	 * @param registeredClientRepository 上边注入的客户端repository
	 * @return JdbcOAuth2AuthorizationService
	 */
	@Bean
	OAuth2AuthorizationService authorizationService(JdbcTemplate jdbcTemplate,
			RegisteredClientRepository registeredClientRepository) {
		// 基于db的oauth2认证服务,还有一个基于内存的服务实现InMemoryOAuth2AuthorizationService
		return new JdbcOAuth2AuthorizationService(jdbcTemplate, registeredClientRepository);
	}

	/**
	 * 配置基于db的授权确认管理服务
	 *
	 * @param jdbcTemplate db数据源信息
	 * @param registeredClientRepository 客户端repository
	 * @return JdbcOAuth2AuthorizationConsentService
	 */
	@Bean
	OAuth2AuthorizationConsentService authorizationConsentService(JdbcTemplate jdbcTemplate,
			RegisteredClientRepository registeredClientRepository) {
		// 基于db的授权确认管理服务,还有一个基于内存的服务实现InMemoryOAuth2AuthorizationConsentService
		return new JdbcOAuth2AuthorizationConsentService(jdbcTemplate, registeredClientRepository);
	}

	/**
	 * 配置jwk源,使用非对称加密,公开用于检索匹配指定选择器的JWK的方法
	 *
	 * @return JWKSource
	 */
	@Bean
	@SneakyThrows
	JWKSource<SecurityContext> jwkSource() {
		// 先从redis获取
		String jwkSetCache = redisStrHelpers.get(ConstAuthorizationServerRedis.AUTHORIZATION_JWS_PREFIX_KEY);
		if (ObjectUtils.isEmpty(jwkSetCache)) {
			KeyPair keyPair = generateRsaKey();
			RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
			RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
			RSAKey rsaKey =
					new RSAKey.Builder(publicKey).privateKey(privateKey).keyID(UUID.randomUUID().toString()).build();
			// 生成jws
			JWKSet jwkSet = new JWKSet(rsaKey);
			// 转为json字符串
			String jwkSetString = jwkSet.toString(Boolean.FALSE);
			// 存入redis
			redisStrHelpers.set(ConstAuthorizationServerRedis.AUTHORIZATION_JWS_PREFIX_KEY, jwkSetString);
			return new ImmutableJWKSet<>(jwkSet);
		}
		// 解析存储的jws
		JWKSet jwkSet = JWKSet.parse(jwkSetCache);
		return new ImmutableJWKSet<>(jwkSet);
	}

	/**
	 * 生成rsa密钥对,提供给jwk
	 *
	 * @return 密钥对
	 */
	private static KeyPair generateRsaKey() {
		KeyPair keyPair;
		try {
			KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
			keyPairGenerator.initialize(2048);
			keyPair = keyPairGenerator.generateKeyPair();
		} catch (Exception ex) {
			throw new IllegalStateException(ex);
		}
		return keyPair;
	}

	/**
	 * 配置jwt解析器
	 *
	 * @param jwkSource jwk源
	 * @return JwtDecoder
	 */
	@Bean
	JwtDecoder jwtDecoder(JWKSource<SecurityContext> jwkSource) {
		return OAuth2AuthorizationServerConfiguration.jwtDecoder(jwkSource);
	}

	/**
	 * 添加认证服务器配置,设置jwt签发者、默认端点请求地址等
	 *
	 * @return AuthorizationServerSettings
	 */
	@Bean
	AuthorizationServerSettings authorizationServerSettings() {
		return AuthorizationServerSettings.builder()
				/*
				 * 设置token签发地址(http(s)://{ip}:{port}/context-path,
				 * http(s)://domain.com/context-path)
				 * 如果需要通过ip访问这里就是ip,如果是有域名映射就填域名,通过什么方式访问该服务这里就填什么
				 */
				.issuer("http://127.0.0.1:8888")
				.build();
	}
}